<?php
/**
 * KvFramework router class
 * @package KvFramework_v2
 * @subpackage KvF_Router
 * @author Greg McWhirter <gsmcwhirter@gmail.com>
 * @copyright Copyright &copy; 2006, Greg McWhirter
 * @version $Revision$
 */

load_files(
  KVF_FRAMEWORK."/kvf_router/kvf_route.class.php",
  KVF_FRAMEWORK."/kvf_router/exceptions/kvf_router.exception.php"
);

/**
 * The framework router
 *
 * @package KvFramework_v2
 * @subpackage KvF_Router
 */
final class KvfRouter
{

  /**
   * Holds the singleton instance
   *
   * @var KvfBaseRouter
   */
  private static $_instance = null;

  /**
   * Holds the array of defined routes sorted by complexity
   *
   * @var array
   */
  private $_routemap = array();

  /**
   * Holds the array of defined routes in entry order
   * @var array
   */
  private $_reconstrmap = array();

  /**
   * Flag for whether or not routing has already occurred.
   *
   * @var boolean
   */
  private $_routed = false;

  /**
   * Base of the uri for url reconstruction
   *
   * @var string
   */
  private $_baseuri = "";

  /**
   * Holds the inflector instance
   * @var KvfBaseInflector
   */
  private $_INFLECTOR = null;

  /**
   * Constructor - private to implement singleton pattern
   * @param string $kvfurl
   * @param KvfBaseInflector $inflector
   *
   */
  private function __construct($kvfurl, KvfBaseInflector $inflector)
  {
    $this->_INFLECTOR = $inflector;

    /* Load the route map */
    $lines = file(KVF_INCLUDES."/configs/routes.config");
    foreach($lines as $line)
    {
      $line = trim($line);
      if(substr($line,0,1) == "#" || empty($line))
      {
        continue;
      }

      $t = explode("-->", trim($line), 2);
      $pattern = trim($t[0]);
      $actmap = trim((array_key_exists(1, $t)) ? $t[1] : "");
      $pattern = trim(preg_replace(array("#^[/]*#","#[/]*$#"),array("",""),$pattern));
      $patparts = (empty($pattern)) ? array() : explode("/", $pattern);
      $actparts = (empty($actmap)) ? array() : explode(",", $actmap);
      $forces = array();

      foreach($actparts as $act)
      {
        list($k,$v) = explode("=", $act);
        $k = trim($k);
        if(substr($k,0,1) == ":")
        {
          $forces[substr($k,1)] = trim($v);
        }
      }

      if(!array_key_exists(count($patparts), $this->_routemap))
      {
        $this->_routemap[count($patparts)] = array();
      }

      $this->_routemap[count($patparts)][] = new KvfRoute($patparts,$forces);
      $this->_reconstrmap[] = new KvfRoute($patparts, $forces);
    }

    $ruri = preg_replace("#\?.*$#", "", urldecode($_SERVER["REQUEST_URI"]));

    $sp = (!empty($kvfurl)) ? strpos($ruri, $kvfurl) : false;
    if($sp === false)
    {
      //$t1 = $_SERVER["REQUEST_URI"];
      $t1 = $ruri;
    }
    else
    {
      //$t1 = substr($_SERVER["REQUEST_URI"], 0, $sp);
      $t1 = substr($ruri, 0, $sp);
    }

    $this->_baseuri = preg_replace(array("#^[/]*#","#[/]*$#"),array("",""),$t1);
    if(!empty($this->_baseuri))
    {
      $this->_baseuri = "/".$this->_baseuri;
    }
  }

  /**
   * Singleton pattern constructor / retreiver
   *
   * @return KvfBaseRouter
   */
  public static function &Instance($kvfurl, $inflector)
  {
    if(!($inflector instanceOf KvfBaseInflector))
    {
      throw new KvfRouterException("Inflector passed was not of an acceptable type.", KvfRouterException::NoInflector);
    }

    if(!(self::$_instance instanceOf KvfRouter))
    {
      $c = __CLASS__;
      self::$_instance = new $c($kvfurl, $inflector);
    }
    return self::$_instance;
  }

  /**
   * Execute a route
   *
   * @param string $request The requested uri
   * @throws KvfRouterException
   */
  public function DoRoute($request)
  {
    if(!$this->_routed)
    {
      //if(substr($request,0,1) == "/"){$request = substr($request,1);}
      //if(substr($request,-1) == "/"){$request = substr($request,0,-1);}
      $request = preg_replace(array("#^[/]*#","#[/]*$#"),array("",""), $request);
      $request = preg_replace("#\.html$#","", $request);
      $rparts = (empty($request)) ? array() : explode("/", $request);
      if(array_key_exists(count($rparts), $this->_routemap))
      {
        $found = null;
        foreach($this->_routemap[count($rparts)] as $route)
        {
          $t = $route->Matches($rparts);
          if($t!==false)
          {
            $found = $t;
            break;
          }
        }

        if(is_null($found))
        {
          throw new KvfRouterException("Unable to find a route matching the request.", KvfRouterException::NoRouteFound);
        }
        else
        {
          $this->_routed = true;
          $this->ExecuteRoute($found["module"], $found["action"], $found["params"]);
        }
      }
      else
      {
        throw new KvfRouterException("Unable to find a route with the proper number of arguments.", KvfRouterException::NoRouteFound);
      }
    }
    else
    {
      throw new KvfRouterException("Routing has already been performed for this request.", KvfRouterException::RoutingPerformed);
    }
  }

  /**
   * Actually execute a route
   *
   * @param string $_module
   * @param string $_action
   * @param array $_params
   * @throws KvfRouterException
   */
  public function ExecuteRoute($_module, $_action, array $_params = array())
  {
    if(!is_null($_module) && !empty($_action))
    {
      KvfBase::$PARAMS = array_merge(KvfBase::$PARAMS, $_params);

      list($_module, $_ContentData, $_WasRedirect) = $this->ExecuteModuleAction($_module, $_action);

      $actionclass = $this->_INFLECTOR->Underscore2Camel($_module."_module")."_".$this->_INFLECTOR->Underscore2Camel($_action."_action");
      $_UseLayout = (defined($actionclass."::UseLayout")) ? constant($actionclass."::UseLayout") : true;

      $param = KvfModule::GetLayoutParams();

      if(!$_WasRedirect && CONFIG::USE_LAYOUT && $_UseLayout)
      {
        $_lo = (is_string($_UseLayout)) ? $_UseLayout : CONFIG::DEFAULT_LAYOUT;

        $this->ExecuteRouteWrapper1($param, $_lo, $_ContentData);
      }
      else
      {
        $this->ExecuteRouteWrapper2($param, $_ContentData);
      }
    }
    else
    {
      throw new KvfRouterException("Matching route did not yield a module to which to route.", KvfRouterException::NoRouteAction);
    }
  }

  protected function ExecuteRouteWrapper1($param, $_lo, $_ContentData)
  {
    include KVF_LAYOUTS_DIR."/".$_lo."/header.phtml";
    echo $_ContentData;
    include KVF_LAYOUTS_DIR."/".$_lo."/footer.phtml";
  }

  protected function ExecuteRouteWrapper2($param, $_ContentData)
  {
    echo $_ContentData;
  }

  /**
   * Execute an action for a module
   *
   * @param string $module
   * @param string $action
   * @return array
   */
  protected function ExecuteModuleAction($module, $action)
  {
    $t = $this->_INFLECTOR->Underscore2Camel($module."_module");
    $inst = new $t();
    $ret = $inst->DoAction($action, $_SERVER["REQUEST_METHOD"]);
	  $res = $ret[0];
	  $module = $ret[1];
	  $redir = $ret[2];
    return array($module, $res, $redir);
  }

  /**
   * Generate the URL for a certain module and action
   *
   * @param string $_module
   * @param string $_action
   * @param array $_params
   * @param array $_params2
   * @return string
   */
  public function UrlFor($_module, $_action, array $_params = array(), array $_params2 = array(), $_forlink = true)
  {
    //ksort($this->_routemap);
    //foreach($this->_routemap as $c => $routes)
    foreach($this->_reconstrmap as $route)
    {
      //foreach($routes as $route)
      //{
        $_t = $route->GetFixedMap();
        if(array_key_exists("module",$_t) && $_t["module"] != $_module)
        {
          continue;
        }
        elseif(array_key_exists("action",$_t) && $_t["action"] != $_action)
        {
          continue;
        }
        else
        {
          $_tp = $_t;
          $ok = true;
          $acctfor = array();
          foreach($_params as $p => $v)
          {
            if(!in_array($p, array("module","action")))
            {
              if(!array_key_exists($p, $_tp) || (array_key_exists($p, $_tp) && $_tp[$p] != $v))
              {
                $acctfor[] = $p;
                break;
              }
            }
          }

          foreach($_tp as $p => $v)
          {
            if(!in_array($p, array("module","action")))
            {
              if(!array_key_exists($p, $_params) || (array_key_exists($p, $_params) && $_params[$p] != $v))
              {
                $ok = false;
                break;
              }
            }
          }

          if(!$ok)
          {
            continue;
          }
        }

        $_t2 = $route->ReconstructRequires();

        foreach($acctfor as $p)
        {
          if(!in_array($p, $_t2))
          {
            $ok = false;
          }
        }

        if(!$ok)
        {
          continue;
        }

        sort($_t2);
        $__t2 = array_intersect($_t2, array_keys(array_merge($_params, array("module" => "", "action" => ""))));
        sort($__t2);

        if($_t2 != $__t2)
        {
          continue;
        }

        //The route is a match now

        return $this->_baseuri.$route->Reconstruct(array_merge(array("module" => $_module, "action" => $_action), $_params), $_params2, $_forlink);

      //}
    }
  }

  public function ShowAllRoutes()
  {
    $ret = array();
    //foreach($this->_routemap as $c => $routes)
    foreach($this->_reconstrmap as $routes)
    {
      foreach($routes as $route)
      {
        $ret[] = implode("/",$route->GetParts())." : ".assoc_implode(", "," => ", $route->GetFixedMap());
      }
    }
    return $ret;
  }

}

?>
